/*
 * get information through "icoctl"
 * if (ioctl(skfd, SIOCGIFMETRIC, &ifr) < 0)
 * SIOCGIFMTU, SIOCGIFMAP, SIOCGIFMAP, SIOCGIFTXQLEN,
 * SIOCGIFDSTADDR SIOCGIFBRDADDR SIOCGIFNETMASK ...
 * 	SIOCGIFCONF currently seems to only work properly on AF_INET sockets
 */

#include <sys/ioctl.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <time.h>

#include "af.h"
#include "interface.h"
#include "../tinyxml/tinyxml.h"

//int addr_family = 0; /* currently selected AF        */

char strTmp[128];
time_t curTime;

TiXmlDocument xmlDoc("interfaces.xml"); // 建立一个XML文件
TiXmlDeclaration Declaration("1.0", "UTF-8", "yes"); // 声明XML的属性

TiXmlNode * pNode = NULL;
TiXmlElement* pRootElm = NULL;
TiXmlElement* pChildElm1 = NULL;
TiXmlElement* pChildElm2 = NULL;
TiXmlElement* pChildElm3 = NULL;
TiXmlText* pText = NULL; // 一个指向Text的指针

int interfaceIndex = 0;

extern int skfd;
extern int sockets_open(int family); // from socket.c

extern int get_socket_for_af(int af);

static struct interface *int_list, *int_last;
static int if_readconf(void);
static char *get_name(char *name, char *p);

/* if interface name already exit in the list, then ignore it
 * else add it in the lists
 */
static struct interface *add_interface(char *name)
{
	struct interface *ife, **nextp, *newp;

	for (ife = int_last; ife; ife = ife->prev)
	{
		int n = strcmp(ife->name, name);
		if (n == 0)
			return ife;
		if (n < 0)
			break;
	}

	newp = (interface *) calloc(sizeof(*newp), 1);
	if (!newp)
	{
		printf("[interface.c] error in calloc...");
		exit(1);
	}

	strncpy(newp->name, name, IFNAMSIZ);
	newp->name[IFNAMSIZ - 1] = '\0'; //make sure it ends with the '\0'

	nextp = ife ? &ife->next : &int_list;
	newp->prev = ife;
	newp->next = *nextp;
	if (newp->next)
		newp->next->prev = newp;
	else
		int_last = newp;
	*nextp = newp;
	return newp;
}

/*
 * read all interface names from /proc/net/dev
 */
static int if_readlist_proc()
{
	static int proc_read;
	FILE *fh;
	char buf[512];
	struct interface *ife;

	if (proc_read) // if read before, return
		return 0;
	proc_read = 1;

	fh = fopen("/proc/net/dev", "r");
	if (!fh)
	{
		printf("interface.c: Error reading /proc/net/dev %s\n", strerror(errno));
		return if_readconf(); // if /proc/net/dev doesn't exit, we use another way to read the interface
		// with available IP address
	}

	fgets(buf, sizeof buf, fh); /* eat line */
	fgets(buf, sizeof buf, fh);

	while (fgets(buf, sizeof buf, fh))
	{
		char *s, name[IFNAMSIZ];
		s = get_name(name, buf);
		ife = add_interface(name);
	}

	fclose(fh);
	return 0;
}

int if_readlist(void)
{
	int err = if_readlist_proc();
	if (!err)
		err = if_readconf();
	return err;
}

static int if_readconf(void)
{
	int numreqs = 30;
	struct ifconf ifc;
	struct ifreq *ifr;
	int n, err = -1;
	int skfd;

	skfd = get_socket_for_af(AF_INET);
	if (skfd < 0)
	{
		fprintf(stderr, ("warning: no inet socket available: %s\n"), strerror(
				errno));
		/* Try to soldier on with whatever socket we can get hold of.  */
		skfd = sockets_open(0);
		if (skfd < 0)
			return -1;
	}

	ifc.ifc_buf = NULL;
	while (1)
	{
		ifc.ifc_len = sizeof(struct ifreq) * numreqs;
		ifc.ifc_buf = (char *) realloc(ifc.ifc_buf, ifc.ifc_len); // How can I confirm this operation?

		if (ioctl(skfd, SIOCGIFCONF, &ifc) < 0)
		{
			perror("SIOCGIFCONF");
			goto out;
		}
		if (ifc.ifc_len == sizeof(struct ifreq) * numreqs)
		{ /* assume it overflowed and try again */
			numreqs += 10;
			continue;
		}
		break;
	}

	ifr = ifc.ifc_req;

	//printf("ifc.ifc_len: %d\n", ifc.ifc_len);

	for (n = 0; n < ifc.ifc_len; n += sizeof(struct ifreq))
	{
		//printf("ifr.ifr_name: %s\n", ifr->ifr_name);

		add_interface(ifr->ifr_name);
		ifr++;
	}
	//exit(0);

	err = 0;

	out: free(ifc.ifc_buf);
	return err;
}

static char *get_name(char *name, char *p)
{
	while (isspace(*p))
		p++;
	while (*p)
	{
		if (isspace(*p))
			break;
		if (*p == ':')
		{ /* could be an alias */
			char *dot = p, *dotname = name;
			*name++ = *p++;
			while (isdigit(*p))
				*name++ = *p++;
			if (*p != ':')
			{ /* it wasn't, backup */
				p = dot;
				name = dotname;
			}
			if (*p == '\0')
				return NULL;
			p++;
			break;
		}
		*name++ = *p++;
	}
	*name++ = '\0';
	return p;
}

int for_all_interfaces(int(*doit)(struct interface *))
{
	struct interface *ife;

	if (!int_list && (if_readlist() < 0))
		return -1;
	for (ife = int_list; ife; ife = ife->next)
	{
		int err = doit(ife);
		if (err)
			return err;
	}
	return 0;
}

/* Fetch the interface configuration from the kernel. */
int if_fetch(struct interface *ife)
{
	struct ifreq ifr;
	int fd;
	char *ifname = ife->name;

	strcpy(ifr.ifr_name, ifname);
	if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0)
		return (-1);
	ife->flags = ifr.ifr_flags;

#if HAVE_AFINET
	/* IPv4 address? */
	fd = get_socket_for_af(AF_INET);
	if (fd >= 0)
	{
		strcpy(ifr.ifr_name, ifname);
		ifr.ifr_addr.sa_family = AF_INET;
		if (ioctl(fd, SIOCGIFADDR, &ifr) == 0)
		{
			ife->has_ip = 1;
			ife->addr = ifr.ifr_addr;
		} else
			memset(&ife->addr, 0, sizeof(struct sockaddr));
	}
#endif

	return 0;
}

int do_if_fetch(struct interface *ife)
{
	if (if_fetch(ife) < 0)
	{
		char *errmsg;
		if (errno == ENODEV)
		{
			/* Give better error message for this case. */
			errmsg = ("Device not found");
		} else
		{
			errmsg = strerror(errno);
		}
		fprintf(stderr, ("%s: error fetching interface information: %s\n"),
				ife->name, errmsg);
		return -1;
	}
	return 0;
}

int do_if_print(struct interface *ife)
{
	int res;

	res = do_if_fetch(ife);
	if (res >= 0)
	{
		if (ife->flags & IFF_UP) // only print available network interfaces
			ife_print(ife);
	}
	return res;
}

void ife_print(struct interface *ptr)
{
	struct aftype *ap;
	int hf;

#if HAVE_AFINET6
	FILE *f;
	char addr6[40], devname[20];
	int plen, scope, dad_status, if_idx;
	char addr6p[8][5];
#endif

	ap = get_afntype(ptr->addr.sa_family);
	if (ap == NULL)
		ap = get_afntype(0);

	hf = ptr->type;

	//	printf(("%s\n "), ptr->name);

	pChildElm2 = new TiXmlElement("interface"); // child level 2

	sprintf(strTmp, "%d", interfaceIndex++);
	pChildElm2->SetAttribute("index", strTmp);
	pNode = pRootElm->InsertEndChild(*pChildElm2);

	pChildElm3 = new TiXmlElement("wiredType");
	pText = new TiXmlText("wired");
	pChildElm3->InsertEndChild(*pText);
	pNode ->InsertEndChild(*pChildElm3);
	delete pChildElm3;
	delete pText;

#if HAVE_AFINET
	if (ptr->has_ip)
	{
		//		printf(("%s addr:%s "), ap->name, inet_ntoa(
		//				((struct sockaddr_in *) (&ptr->addr))->sin_addr));
		//		printf("\n");

		//delete pChildElm3 ;
		//		pChildElm3 = new TiXmlElement("addrType");
		//		//delete pText;									// free the previous space
		//		pText = new TiXmlText(ap->name); // inet4
		//		pChildElm3->InsertEndChild(*pText);
		//		pNode ->InsertEndChild(*pChildElm3);

		//delete pChildElm3 ;
		pChildElm3 = new TiXmlElement("addr");
		pChildElm3->SetAttribute("addrType", "inet");
		//delete pText;									// free the previous space
		pText = new TiXmlText(inet_ntoa(
				((struct sockaddr_in *) (&ptr->addr))->sin_addr)); // inet4
		pChildElm3->InsertEndChild(*pText);
		pNode ->InsertEndChild(*pChildElm3);
		delete pChildElm3;
		delete pText;
	}
#endif

#if HAVE_AFINET6
	/* FIXME: should be integrated into interface.c.   */

	if ((f = fopen("/proc/net/if_inet6", "r")) != NULL)
	{
		//		pChildElm3 = new TiXmlElement("addrType");
		//		//			    //delete pText;									// free the previous space
		//		pText = new TiXmlText("inet6");//ap->name) ;			// inet6
		//		pChildElm3->InsertEndChild(*pText);
		//		pNode ->InsertEndChild(*pChildElm3);

		while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n",
				addr6p[0], addr6p[1], addr6p[2], addr6p[3], addr6p[4],
				addr6p[5], addr6p[6], addr6p[7], &if_idx, &plen, &scope,
				&dad_status, devname) != EOF)
		{
			if (!strcmp(devname, ptr->name))
			{
				sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s", addr6p[0], addr6p[1],
						addr6p[2], addr6p[3], addr6p[4], addr6p[5], addr6p[6],
						addr6p[7]);
				//				printf(("inet6 addr: %s/%d"), addr6, plen);
				//				printf((" Scope:"));
				//				switch (scope)
				//				{
				//					case 0:
				//						printf(("Global"));
				//						break;
				//					case IPV6_ADDR_LINKLOCAL:
				//						printf(("Link"));
				//						break;
				//					case IPV6_ADDR_LOOPBACK:
				//						printf(("Host"));
				//						break;
				//					default:
				//						printf(("Unknown"));
				//				}
				//				printf("\n");

				pChildElm3 = new TiXmlElement("addr");
				pChildElm3->SetAttribute("addrType", "inet6");
				pText = new TiXmlText(addr6); // IPv6 addr
				pChildElm3->InsertEndChild(*pText);
				pNode ->InsertEndChild(*pChildElm3);
				delete pChildElm3;
				delete pText;
			}
		}
		fclose(f);
	}
#endif

	/* DONT FORGET TO ADD THE FLAGS IN ife_print_short, too */

	//	if (ptr->flags & IFF_UP)
	//		printf(("UP "));
	//	if (ptr->flags & IFF_BROADCAST)
	//		printf(("BROADCAST "));
	//	if (ptr->flags & IFF_LOOPBACK)
	//		printf(("LOOPBACK "));
	//	if (ptr->flags & IFF_RUNNING)
	//		printf(("RUNNING "));
	//	if (ptr->flags & IFF_MULTICAST)
	//		printf(("MULTICAST "));
	/* DONT FORGET TO ADD THE FLAGS IN ife_print_short */
	delete pChildElm2;
	//printf("\n");
}

int main(int argc, char **argv)
{
	/* Create a channel to the NET kernel. */
	if ((skfd = sockets_open(0)) < 0)
	{
		perror("socket");
		exit(1);
	}

	xmlDoc.InsertEndChild(Declaration); // 写入基本的XML头结构

	pRootElm = new TiXmlElement("contextRequest");
	pNode = xmlDoc.InsertEndChild(*pRootElm); // first level (root), xmlDoc insert it
	pRootElm = pNode->ToElement();

	pChildElm1 = new TiXmlElement("operation");
	pText = new TiXmlText("strore");
	pChildElm1->InsertEndChild(*pText); // child level 1
	pRootElm->InsertEndChild(*pChildElm1);
	delete pChildElm1;
	delete pText;

	pChildElm1 = new TiXmlElement("details");
	pChildElm1->SetAttribute("msgType", "interfaceInfo");
	pNode = pRootElm->InsertEndChild(*pChildElm1);
	pRootElm = pNode->ToElement();
	delete pChildElm1;

	pChildElm2 = new TiXmlElement("nodeId"); // child level 2
	pText = new TiXmlText("1111222233334444");
	pChildElm2->InsertEndChild(*pText);
	pRootElm->InsertEndChild(*pChildElm2);
	delete pChildElm2;
	delete pText;

	time(&curTime);
	pChildElm2 = new TiXmlElement("time"); // child level 2
	sprintf(strTmp, "%d", (int) curTime);
	pText = new TiXmlText(strTmp);
	pChildElm2->InsertEndChild(*pText);
	pRootElm->InsertEndChild(*pChildElm2);
	delete pChildElm2;
	delete pText;

	int err = for_all_interfaces(do_if_print);//, &opt_a);
	(void) close(skfd);

	TiXmlPrinter printer;
	xmlDoc.Accept(&printer);
	int t = printer.Size() + 1; // the size of useful string
	char *a = (char *) malloc(t);
	strcpy(a, printer.CStr());
	printf("%d\n%s \n", t, a);

	exit(err < 0);
}
